"""
Provides facilities for rendering text.

Render words one at a time, then display them by fading in one character's width, or an
arbitrary number of pixels, at a time,
over a few ticks. This will deal with width issues and avoid making the font resoruces
contested. Perhaps also render stuff with a lead-in, to give the text more of a flowing feel,
without any weirdness because of clipping characters.

Another thing we probably need is support for arbitrary text labels, like the layered image system.
"""
import warnings

import pygame

import canvas_elements
from canvas_elements import (Area, Rectangle)
from events import Event
import font

_DEFAULT_LINE_COUNT = 4

class TextRenderer(object):
    """
    Provides a stateful construct that renders blocks of text over time.
    """
    _offset_x = 0
    _offset_y = 0
    
    _target_text = None
    
    def __init__(self, target_text):
        """
        `target_text` is the text to ultimately be displayed, rendered as it should be displayed.
        """
        self._surface = pygame.Surface(target_text.get_size())
        self._target_text = target_text
        
    def tick(self, dt):
        """
        Updates the internal surface to draw text over time.
        
        When ``False`` is returned, the target text should be added to the composite layer.
        """
        self._surface.blit(self._target_text, (-self._offset_x, -self._offset_y))
        return False
        
    def get_surface(self):
        """
        The rendered surface.
        
        The number of pixels the surface uses as padding, with offsets relative to the top-left of
        rendered text.
        """
        return (self._surface, (self._offset_x, self._offset_y))
        
    def get_target_text(self):
        return self._target_text
        
class Textbox(canvas_elements.Element):
    """
    Provides a generic means of presenting text.
    """
    _kwargs = None
    _screen = None
    _fonts = None
    _left_to_right = True
    _surface_boundaries = None
    _offset_x = None
    _offset_y = None
    _line_height = None
    _line_count = None
    _collected_text = None #A collection of all rendered lines of text, without effects.
    _renderer = None
    _delimiter = None
    
    def __init__(self, screen, renderer=TextRenderer, delimiter=' ', line_count=_DEFAULT_LINE_COUNT, left_to_right=True, fonts={}, **kwargs):
        canvas_elements.Element.__init__(self, screen, 1000000)
        
        self._screen = screen
        self._kwargs = kwargs
        self._left_to_right = left_to_right
        self._line_count = line_count
        
        self._fonts = fonts or {'default': (None, None)}
        self._prepare_surface()
        
        self._collected_text = [[]]
        
        self._delimiter = delimiter or ''
        self._renderer = renderer()
        
    @property
    def delimiter(self):
        return self._delimiter
        
    def _prepare_surface(self):
        (self._surface, self._surface_boundaries) = self._prepare_bordered_surface()
        #Cause the next character to appear at the origin
        if self._left_to_right:
            self._offset_x = self._surface_boundaries.left
        else:
            self._offset_x = self._surface_boundaries.right
        self._offset_y = self._surface_boundaries.top
        
    def _compute_text_area_size(self, border_width):
        (width, height) = self._screen.get_size()
        self._line_height = font.get_height(self._fonts.get('default'))
        return (
         width - (self._kwargs.get('padding_horizontal', 0) + border_width),
         self._line_count * self._line_height
        )
        
    def _prepare_bordered_surface(self):
        (width, height) = self._compute_text_area_size(2)
        width += 2
        height += 2
        surface = canvas_elements.Surface((width, height)) #Alpha in subclasses
        
        surface.fill((0, 0, 0))
        pygame.draw.rect(surface, (0, 0, 255), (0, 0, width, height), 1)
        
        #Access _kwargs to get things like display name
        
        return (surface, Area(2, 2, width - 2, height - 2))
        
    def _position_text(self, token_width):
        """
        Ensures that proper positioning is used to render the token, given
        `token_width` in pixels.
        
        A return value of ``False`` means that positioning is impossible.
        """
        if self._left_to_right:
            if self._offset_x + token_width > self._surface_boundaries.right:
                if self._offset_x != self._surface_boundaries.left: #Can wordwrap
                    self._offset_y += self._line_height
                    self._offset_x = self._surface_boundaries.left
        else:
            if self._offset_x - token_width < self._surface_boundaries.left:
                if self._offset_x != self._surface_boundaries.right: #Can wordwrap
                    self._offset_y += self._line_height
                    self._offset_x = self._surface_boundaries.right
                    
        if self._offset_y >= self._surface_boundaries.bottom:
            return False
        return True
        
    def queue_event(self, event):
        """
        Given an `event`, adds it to the queue that gets processed with every
        call to ``tick()``; events may span multiple ticks.
        """
        self._event_queue.append(event)
        
    def queue_text(self, text):
        self.queue_event(TextEvent(self, text))
        
    def add_text(self, text):
        """
        Deprecated in favour of TextRenderers and queue_event.
        
        May be reimplemented as a convenience function for these concepts.
        """
        self._collected_text[-1].append(text)
        
        render_font = self._fonts['default']
        delimiter_width = font.get_width(render_font, self._delimiter)
        for token in text.split(self._delimiter):
            if not self._left_to_right:
                token = token[::-1]
            fragment = font.render(render_font, token)
            fragment_width = fragment.get_width()
            
            if not self._position_text(fragment_width):
                warnings.warn("Truncated out-of-bounds text: " + text)
                return
                
            if self._left_to_right:
                self._surface.blit(fragment, (self._offset_x, self._offset_y))
                self._offset_x += fragment_width + delimiter_width
            else:
                self._surface.blit(fragment, (self._offset_x - fragment_width, self._offset_y))
                self._offset_x -= fragment_width + delimiter_width
                
    def clear(self):
        """
        Note that any collected text is preserved, so that, when rendered generically, the box height is dynamic.
        
        For this to work, image-based textboxes should not override the generic rendering function.
        """
        self._collected_text.append([])
        self._prepare_surface()
        
class TranslucentTextbox(Textbox):
    def _prepare_bordered_surface(self):
        (width, height) = self._compute_text_area_size(2)
        width += 2
        height += 2
        surface = pygame.surface.Surface((width, height), flags=pygame.SRCALPHA) #Alpha in subclasses
        
        surface.fill((0, 0, 0, 127))
        pygame.draw.rect(surface, (0, 0, 255), (0, 0, width, height), 1)
        
        #Access _kwargs to get things like display name
        
        return (surface, (2, 2, width - 2, height - 2))
        
class ImageTextbox(Textbox):
    """
    Renders text on a static image
    """
    
class TextEvent(Event):
    _textbox = None #The textbox to be affected
    _text = None #The text to be added
    
    def __init__(self, textbox, text):
        Event.__init__(self, blocking=True)
        self._textbox = textbox
        self._text = text.split(textbox.delimiter)
        
    def tick(self, dt):
        if self._text:
            self._textbox.add_text(self._text.pop(0))
            return True
        return False
        
